package com.opengate.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.opengate.base.authtoken.AuthTokenHelper;
import com.opengate.base.authtoken.JwtHelper;
import com.opengate.base.authtoken.UserToken;
import com.opengate.base.constants.AppConstants;
import com.opengate.base.contextholder.SpringContextHolder;
import com.opengate.base.result.Result;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author 曳戈泰尔
 * @version 1.0
 * @description 全局过滤器
 * @date 2023/4/27 9:53 PM
 */
@Slf4j
@Order(-1)
@Component
@Import({SpringContextHolder.class})
public class AuthFilter implements GlobalFilter {

    /** 注入配置文件 */
    private final AuthProperties authProperties;

    @Autowired
    public AuthFilter(AuthProperties authProperties) {
        this.authProperties = authProperties;
    }

    /** 鉴权过滤器 */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 获取请求路径
        String url = exchange.getRequest().getURI().getPath();
        // 初始化路径匹配器
        AntPathMatcher matcher = new AntPathMatcher();

        // 资源名单
        for (String s : authProperties.getResource()) {
            // 如果匹配成功，则通过 filter 方法将请求转发给下一个处理器。
            if (matcher.match(s, url)) {
                return chain.filter(exchange);
            }
        }

        // 阻塞名单
        for (String block : authProperties.getBlocks()) {
            if (matcher.match(block, url)) {
                log.error("{}请求不被允许，触发阻塞名单", url);
                return this.unPassResponse(exchange, HttpStatus.FORBIDDEN.value(), "请求不被允许，触发阻塞名单");
            }
        }

        // 白名单验证
        boolean isWhite = false;
        for (String white : authProperties.getWhites()) {
            if (matcher.match(white, url)) {
                isWhite = true;
                break;
            }
        }

        // 头部校验
        HttpHeaders headers = exchange.getRequest().getHeaders();

        // 验证白名单和token
        String token = headers.getFirst(AppConstants.AUTHORIZATION_HEADER);
        boolean isExistToken = StringUtils.isNotBlank(token);

        // 不存在token
        if (!isExistToken) {
            // 不是放行的白名单
            if (!isWhite) {
                log.error("{}，token不能为空", url);
                return this.unPassResponse(exchange, HttpStatus.UNAUTHORIZED.value(), "token不能为空");
            }
        }

        // 存在token
        else {
            // 解析token，获取用户id
            Object uid = null;
            try {
                Claims claims = JwtHelper.parseToken(token, AppConstants.APP_SECRET);
                uid = claims.get(AppConstants.USER_ID);
            } catch (Exception e) {
                log.error("{}，token={}不合法", url, token, e);
            }

            // 用户id为空
            if (uid == null) {
                // 不是放行的白名单
                if (!isWhite) {
                    log.info("{}，token={}，解析用户有误", url, token);
                    return this.unPassResponse(
                            exchange, HttpStatus.UNAUTHORIZED.value(), "token解析用户有误");
                }
            }

            // 用户id不为空
            // 处理正确的token登录逻辑
            else {
                // 获取用户有效的登录信息
                long curTime = System.currentTimeMillis();
                UserToken.UserDto userDto = AuthTokenHelper.getAuthToken(uid.toString(), curTime);

                // 登录信息已过期
                if (userDto == null) {
                    // 不是放行的白名单
                    if (!isWhite) {
                        return this.unPassResponse(exchange, 499, "登录状态已过期");
                    }
                }

                // 请求header的token与登录信息的token不一致
                else if (!userDto.getToken().equals(token)) {
                    // 不是放行的白名单
                    if (!isWhite) {
                        return this.unPassResponse(exchange, 499, "登录状态已过期");
                    }
                }

                // 登录信息未过期
                // 判断是否刷新登录信息
                else {
                    // 登录过期时间小于1天，刷新登录信息
                    if (userDto.getExpireTime() < (curTime + 24 * 60 * 60 * 1000)) {
                        AuthTokenHelper.refreshAuthToken(uid.toString(), curTime);
                    }

                    // 透传 携带用户登录信息
                    return chain.filter(this.mutableExchange(exchange, uid.toString()));
                }
            }
        }

        //  透传 不带用户登录信息
        return chain.filter(this.mutableExchange(exchange, null));
    }

    /** 设置用户信息到请求 Header */
    private ServerWebExchange mutableExchange(ServerWebExchange exchange, String uid) {

        // 设置用户信息到请求
        ServerHttpRequest mutableReq =
                exchange.getRequest().mutate().header(AppConstants.USER_ID, uid).build();

        // 返回新的 ServerWebExchange 对象
        return exchange.mutate().request(mutableReq).build();
    }

    /** 未通过鉴权返回结果 */
    private Mono<Void> unPassResponse(ServerWebExchange exchange, int code, String msg) {

        // 获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        // 设置响应头
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        // 设置响应码
        response.setStatusCode(HttpStatus.OK);

        // 返回响应结果
        // 将一个Result对象序列化成JSON格式的数据流，并将其作为响应体返回给客户端。
        return response.writeWith(
                Mono.fromSupplier(
                        () -> {
                            // 先创建了一个DataBufferFactory对象，它是用来生成DataBuffer数据缓冲区的工厂类。
                            // 然后通过bufferFactory.wrap()方法将一个Result对象转换成一个DataBuffer对象
                            // 最终返回一个包含这个DataBuffer对象的Mono对象。
                            DataBufferFactory bufferFactory = response.bufferFactory();
                            return bufferFactory.wrap(JSON.toJSONBytes(Result.fail(code, msg)));
                        }));
    }
}
